import { RegExpCoder } from 'regexp-coder'

const splitter: string = '|'
const escaper: string = '\\'

export class MarkdownParser {
	/**
	 * The splitter expression to split a markdown row to cells
	 * `/(?<!(?:^|[^\\|])(?:\\\\|\\\|)*\\)\|/`
	 */
	static markdownCellSplitterExp(): RegExp {
		const noSplitterChar = RegExpCoder.new().negatedSet(
			RegExpCoder.encodeRegExp(splitter, true),
			RegExpCoder.encodeRegExp(escaper, true),
		)
		const qualifiedSplitterChar = RegExpCoder.new().or(
			RegExpCoder.encodeRegExp(`${escaper}${escaper}`),
			RegExpCoder.encodeRegExp(`${escaper}${splitter}`),
		)

		return RegExpCoder.new()
			.negatedLookbehind(
				RegExpCoder.encodeRegExp(splitter),
				new RegExpCoder()
					.or('^', noSplitterChar, { group: true, notRemember: true })
					.join(qualifiedSplitterChar, {
						qualifier: '*',
						notRememberQualifiedItem: true,
						groupQualifiedItem: true,
					})
					.join(RegExpCoder.encodeRegExp(escaper)),
			)
			.toRegExp()
	}

	/**
	 * The regular expression to match a markdown table line row.
	 */
	public static markdownLineRowExp(): RegExp {
		const whitespaceAny = /\s*/
		const headerLineEx = /(\s*)(:?)(-*)(:?)(\s*)/
		const splitterEx = /\|/
		return new RegExpCoder()
			.join(whitespaceAny)
			.join(splitterEx, { qualifier: '?', group: true })
			.join(headerLineEx, splitterEx, { qualifier: '+', group: true })
			.join(headerLineEx, { qualifier: '?', group: true })
			.join(whitespaceAny)
			.enableMatchWhole()
			.toRegExp()
	}

	/**
	 * The regular expression to match a markdown table headers or rows
	 * `/^\s*(\|)?((\s*(([^|\\]|\\\\|\\\|)*)\s*\|)+)(\s*(([^|\\]|\\\\|\\\|)*)\s*)+\s*$/`
	 */
	static markdownRowExp(): RegExp {
		const whitespaceAny = /\s*/
		const noSplitterChar = RegExpCoder.new().negatedSet(splitter, RegExpCoder.encodeRegExp(escaper))
		const qualifiedSplitterChar = RegExpCoder.new().or(
			RegExpCoder.encodeRegExp(`${escaper}${escaper}`),
			RegExpCoder.encodeRegExp(`${escaper}${splitter}`),
		)
		const cell = RegExpCoder.new()
			.join(whitespaceAny)
			.or(noSplitterChar, qualifiedSplitterChar, { qualifier: '*', group: true })
			.join(whitespaceAny)
		return RegExpCoder.new()
			.join(whitespaceAny)
			.join(RegExpCoder.encodeRegExp(splitter), { qualifier: '?', notRemember: true })
			.join(cell, RegExpCoder.encodeRegExp(splitter), { qualifier: '+', group: true })
			.join(cell, { qualifier: '+' })
			.join(whitespaceAny)
			.enableMatchWhole()
			.toRegExp()
	}

	static matchHeader(line: string): RegExpExecArray | null {
		return headerExp.exec(line)
	}

	static matchListItem(line: string): RegExpExecArray | null {
		return listItemExp.exec(line)
	}

	static splitTableRowToCells(line: string): string[] {
		let values = line.split(splitterExp)
		if (values.length > 0 && values[0] === '') {
			values = values.slice(1, values.length)
		}
		if (values.length > 0 && values[values.length - 1] === '') {
			values = values.slice(0, values.length - 1)
		}
		values = values.map((value) => value.trim().replace(/\\(.)/g, '$1'))
		return values
	}

	static testTableHeader(line: string): boolean {
		return tableRowExp.test(line)
	}

	static testTableLine(line: string): boolean {
		return tableLineRowExp.test(line)
	}

	static testTableRow(line: string): boolean {
		return tableRowExp.test(line)
	}
}

const headerExp = RegExpCoder.new().join('(#+)', '\\s+', '(.*)').enableMatchWhole().toRegExp()
const listItemExp = RegExpCoder.new().join('-', '\\s+', '([^:]*)', '\\s*:\\s*', '(.*)').enableMatchWhole().toRegExp()
const tableRowExp = MarkdownParser.markdownRowExp()
const tableLineRowExp = MarkdownParser.markdownLineRowExp()
const splitterExp = MarkdownParser.markdownCellSplitterExp()
